
mod terminate;
mod calculate;
mod web;
mod command;
mod filesystem;
mod eda;
mod email;

pub use terminate::Terminator;
pub use calculate::Calculator;
pub use command::CommandExecutor;
pub use web::WebSearch;
pub use web::UrlReader;
pub use filesystem::FileCreator;
pub use filesystem::FileWritor;
pub use filesystem::FileReader;
pub use email::EmailSender;

use llm_sdk::chat::{FunctionInfo, ToolDefinition, ToolType};
use std::collections::HashMap;
use anyhow::{Result, anyhow};
use serde_json::Value;
use async_trait::async_trait;


#[async_trait]
pub trait Tool {
    fn name(&self) -> String;
    fn description(&self) -> String;
    fn parameters(&self) -> Value;
    fn to_function_define(&self) -> ToolDefinition {
        ToolDefinition {
            r#type: ToolType::Function,
            function: FunctionInfo {
                description: self.description(),
                name: self.name(),
                parameters: self.parameters()
            }
        }
    }

    async fn execute(&self, args: Value) -> Result<Option<String>>;
}

#[derive(Debug)]
pub struct ToolValue {
    pub output: Option<String>,
    pub error: Option<String>,
    pub system: Option<String>,
}

pub struct ToolCollection {
    tools: HashMap<String, Box<dyn Tool>>,
}

impl ToolCollection {
    pub fn new<I: Iterator<Item = Box<dyn Tool>>>(tools: I) -> Self {
        Self {
            tools: tools.map(|tool| (tool.name(), tool)).collect()
        }
    }

    pub async fn execute(&self, name: &str, args: Value) -> Result<Option<String>> {
        match self.get_tool(name) {
            Some(tool) => tool.execute(args).await,   
            None => Err(anyhow!(format!("No exit tool '{}'", name)))
        }
    }

    pub fn to_function_defines(&self) -> Vec<ToolDefinition> {
        self.tools.iter().map(|(_, tool)| tool.to_function_define()).collect()
    }

    pub fn get_tool(&self, name: &str) -> Option<&Box<dyn Tool>> {
        self.tools.get(name)
    }

    pub fn add_tool(&mut self, tool: Box<dyn Tool>) {
        let name = tool.name();
        self.tools.insert(name, tool);
    }

    pub fn add_tools<I: Iterator<Item = Box<dyn Tool>>>(&mut self, tools: I) {
        for tool in tools {
            self.add_tool(tool);
        }
    }
}